import sys
from typing import *
from pathlib import Path
from abc import ABC,abstractmethod
from dataclasses import dataclass
import ramda as R
from PyQt6.QtCore import *
from PyQt6.QtWidgets import *
from PyQt6.QtGui import *
from uxfunc import *
from uxtheme import uxtheme_use
import utils

class Const:
    WINDOW_TITLE_TAIL = f"(Windows-x64) 20231229"
    WINDOW_ICON_PATH = Path("assets/icon.ico")
    WINDOW_WIDTH_RATE = 0.7
    WINDOW_HEIGHT_RATE = 0.8
    IMG_RESULT_ICON_SIZE = 64
    class Layout:
        LEFT_STRETCH = 4
        RIGHT_STRETCH = 8
        DIALOG_RESULT_WIDTH_RATE = 0.4
        DIALOG_RESULT_HEIGHT_RATE = 0.7
    FRAME_START_MS = 500

# Interface for Extern Control
# Data I/O(Ux<->IO) and Event Notify(Ux->IO)
class IO(ABC):
    # for pathview
    @abstractmethod
    def on_selected_dirs(me, dirs:List[str]) -> None: pass
    # for lu-img
    @abstractmethod
    def on_img_check_start(me) -> None: pass
    @abstractmethod
    def on_img_result_tap(me, data) -> None: pass
    @abstractmethod
    def on_img_refresh_result(me) -> None: pass
    #
    # for events
    @abstractmethod
    def on_ready(me) -> None: pass
    @abstractmethod
    def on_frame(me) -> None: pass


# Interface for Ux Present
class UxRoot(ABC):
    # use for extern/widgets only:
    @abstractmethod
    def send(me, target:str, *args, **kwargs) -> None: pass

    # use for extern only:
    @abstractmethod
    def overtime(me, name:str) -> None: pass
    @abstractmethod
    def start(me, io:IO) -> None: pass

    # use for widgets only:
    @abstractmethod
    def need_update(me, name_by_overtime:str) -> bool: pass
    @abstractmethod
    def action_join(me, target_by_send:str, fn:Callable[...,None]) -> None: pass
    @abstractmethod
    def frame_join(me, fn:Callable[[],None]) -> None: pass
    @abstractmethod
    def io(me) -> IO: pass

def popup_error(message:str) -> None:
    QMessageBox.critical(None, "ERROR", message)

class __OnFrame():
    @abstractmethod
    def on_frame(me) -> None:pass

def format_img_title(path:str) -> str:
    img = QImage(path)
    return f"[{utils.format_filesize(img.sizeInBytes())}|{img.width()}x{img.height()}] {Path(path).name}"

class UxPathModel(QFileSystemModel):
    def __init__(me, init_dir:str) -> None:
        super().__init__()
        me.setRootPath(init_dir)
        me.setFilter(QDir.Filter.AllDirs | QDir.Filter.Dirs | QDir.Filter.Drives | QDir.Filter.NoDotAndDotDot)
    def columnCount(me, _) -> int:
        return 1
class UxPathView(QTreeView):
    def __init__(me, root:UxRoot) -> None:
        me.__root = root
        super().__init__()
        me.__model = UxPathModel("")
        me.setModel(me.__model)
        me.setHeaderHidden(True)
        me.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOn)
        me.setSelectionMode(QAbstractItemView.SelectionMode.MultiSelection)
    def selectionChanged(me, selected: QItemSelection, deselected: QItemSelection) -> None:
        paths = [me.__model.filePath(mi) for mi in me.selectedIndexes()]
        me.__root.io().on_selected_dirs(paths)
        return super().selectionChanged(selected, deselected)

class UxDialogResult(QDialog):
    def __init__(me, root:UxRoot, items:List[str]) -> None:
        me.__root = root
        super().__init__()
        me.setModal(True)
        me.setWindowTitle(f"Result Operator")
        screen_rect = QGuiApplication.primaryScreen().geometry()
        width = int(Const.Layout.DIALOG_RESULT_WIDTH_RATE * screen_rect.width())
        height = int(Const.Layout.DIALOG_RESULT_HEIGHT_RATE * screen_rect.height())
        me.setFixedSize(width, height)
        me.setWindowIcon(QIcon(str(Const.WINDOW_ICON_PATH)))
        uxtheme_use(me)
        #
        box = ux_row_layout()
        box.addLayout(me.__make_tools())
        me.__model_files = QStandardItemModel()
        for path in items:
            item = QStandardItem(QIcon(path), format_img_title(path))
            item.setCheckable(True)
            item.setCheckState(Qt.CheckState.Unchecked)
            item.setData(path, Qt.ItemDataRole.UserRole)
            me.__model_files.appendRow(item)
        box.addWidget(ux_expend_y(me.__make_files(me.__model_files)))
        me.setLayout(box)

    def __on_tap_file(me, *_) -> None:
        pass

    def __on_select_all(me, *_) -> None:
        for i in range(me.__model_files.rowCount()):
            item = me.__model_files.item(i)
            item.setCheckState(Qt.CheckState.Checked)

    def __on_select_clear(me, *_) -> None:
        for i in range(me.__model_files.rowCount()):
            item = me.__model_files.item(i)
            item.setCheckState(Qt.CheckState.Unchecked)

    def __on_remove(me, *_) -> None:
        selects = list()
        for i in range(me.__model_files.rowCount()-1,-1,-1):
            item = me.__model_files.item(i)
            if item.checkState() == Qt.CheckState.Checked:
                selects.append((item.data(Qt.ItemDataRole.UserRole), i))
        if len(selects) > 0:
            selects_str = '\n'.join([a[0] for a in selects])
            prompt = f"Remove there files?\n{selects_str}"
            if QMessageBox.StandardButton.Yes == QMessageBox.question(me, "Remove Selected Image", prompt):
                for path,row in selects:
                    Path(path).unlink()
                    me.__model_files.removeRow(row)
                me.__root.io().on_img_refresh_result()

    def __make_tools(me) -> QHBoxLayout:
        box = ux_column_layout()
        btn_remove = ux_pushbutton("Remove Selected", id="img-dialog-result-remove", on_click=me.__on_remove)
        box.addWidget(btn_remove)
        btn_select_all = ux_pushbutton("Select All", on_click=me.__on_select_all)
        box.addWidget(btn_select_all)
        btn_select_clear = ux_pushbutton("Select Clear", on_click=me.__on_select_clear)
        box.addWidget(btn_select_clear)
        return box

    def __make_files(me, model:QStandardItemModel) -> QListView:
        view = QListView()
        view.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOn)
        view.setFlow(QListView.Flow.TopToBottom)
        view.setViewMode(QListView.ViewMode.ListMode)
        view.setIconSize(QSize(Const.IMG_RESULT_ICON_SIZE, Const.IMG_RESULT_ICON_SIZE))
        view.setModel(model)
        view.clicked.connect(me.__on_tap_file)
        return view

class UxJobImage(QWidget,__OnFrame):
    def __init__(me, root:UxRoot) -> None:
        super().__init__()
        box = ux_row_layout(me)
        #
        btn_start = ux_pushbutton("Check with in Selected-Dirs", id="img-check-start")
        btn_start.clicked.connect(lambda:me.__root.io().on_img_check_start())
        box.addWidget(btn_start)
        #
        me.__lab_status = ux_label("", id="img-check-status")
        box.addWidget(me.__lab_status)
        #
        me.__model_result = QStandardItemModel()
        me.__view_result = me.__make_result_box(me.__model_result)
        box.addWidget(me.__view_result)
        #
        root.action_join("img/add-result", me.on_add_result)
        root.action_join("img/clear-result", me.on_clear_result)
        root.action_join("img/set-status", me.on_set_status)
        root.action_join("img/dialog-result", me.on_dialog_result)
        me.__root = root

    def on_add_result(me, icon_path:str, title:str, data) -> None:
        row = QStandardItem(QIcon(icon_path), title)
        row.setData(data, Qt.ItemDataRole.UserRole)
        me.__model_result.appendRow(row)

    def on_clear_result(me) -> None:
        me.__model_result.clear()

    def on_set_status(me, text:str) -> None:
        me.__lab_status.setText(text)

    def on_dialog_result(me, filepaths:List[str]) -> None:
        dialog = UxDialogResult(me.__root, filepaths)
        dialog.exec()

    def __on_tap_result(me, *_) -> None:
        for mi in me.__view_result.selectedIndexes():
            me.__root.io().on_img_result_tap(me.__model_result.data(mi, Qt.ItemDataRole.UserRole))

    def __make_result_box(me, model:QAbstractItemModel) -> QListView:
        view = QListView(me)
        view.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOn)
        view.setFlow(QListView.Flow.TopToBottom)
        view.setViewMode(QListView.ViewMode.ListMode)
        view.setIconSize(QSize(Const.IMG_RESULT_ICON_SIZE, Const.IMG_RESULT_ICON_SIZE))
        view.setModel(model)
        view.clicked.connect(me.__on_tap_result)
        return ux_expend_y(view)

class UxJobs(QTabWidget):
    def __init__(me, root:UxRoot) -> None:
        super().__init__()
        me.addTab(UxJobImage(root), "Image")

class Ux(UxRoot):
    def __init__(me, fps:int):
        me.__frame_fns = list()
        me.__action_map = dict()
        me.__overtimes = set()
        me.__frame_timer = QTimer()
        me.__frame_timer.setInterval(int(1000 / fps))
        me.__qapp = QApplication(sys.argv)

    def start(me, io:IO) -> None:
        try:
            me.__io = io
            me.__window = me.__make()
            QTimer.singleShot(Const.FRAME_START_MS, me.__on_frame_0)
            me.__qapp.exec()
            me.__frame_timer.stop()
        except Exception as e:
            popup_error(str(e))
            raise(e)

    def send(me, target:str, *args, **kwargs) -> None:
        if target in me.__action_map:
            fn = me.__action_map.get(target)
            fn(*args, **kwargs)

    def overtime(me, name:str) -> None:
        me.__overtimes.add(name)

    def io(me) -> IO: return me.__io

    def need_update(me, name_by_overtime:str) -> bool:
        return (name_by_overtime in me.__overtimes)

    def action_join(me, target_by_send:str, fn:Callable[..., None]) -> None:
        if callable(fn):
            me.__action_map[target_by_send] = fn

    def frame_join(me, fn:Callable[[], None]) -> None:
        if callable(fn):
            me.__frame_fns.append(fn)

    def __on_frame(me) -> None:
        # Step-1 Notify Extern Processer
        me.__io.on_frame()
        # Step-2 Frame Processers
        R.for_each((lambda f:f()), me.__frame_fns)
        # Step-3 Reset Overtime-States
        me.__overtimes.clear()

    def __on_frame_0(me) -> None:
        me.__io.on_ready()
        me.__window.show()
        me.__frame_timer.timeout.connect(me.__on_frame)
        me.__frame_timer.start()

    def __make(me) -> QDialog:
        window = QDialog()
        window.setWindowFlag(Qt.WindowType.WindowMinimizeButtonHint, True)
        window.setWindowTitle(f"Letunique {Const.WINDOW_TITLE_TAIL}")
        screen_rect = QGuiApplication.primaryScreen().geometry()
        width = int(Const.WINDOW_WIDTH_RATE * screen_rect.width())
        height = int(Const.WINDOW_HEIGHT_RATE * screen_rect.height())
        window.setFixedSize(width, height)
        window.setWindowIcon(QIcon(str(Const.WINDOW_ICON_PATH)))
        uxtheme_use(window)
        #
        box = ux_column_layout(window)
        box.addWidget(UxPathView(me), Const.Layout.LEFT_STRETCH)
        box.addWidget(UxJobs(me), Const.Layout.RIGHT_STRETCH)
        return window
